#ifndef __FILE_HASH__
#define __FILE_HASH__

#include "md5.hxx"

namespace mm
{

	template < size_t FRAME_SIZE, class Processor >
	long travel_file_data( xm::IKFileStream * kfs, Processor& proc )
	{
		uint64_t fs64 = kfs->Length();
		if( fs64 == LENGTH_ERROR ) return E_FAIL;
		std::vector<uint8_t> buffer(FRAME_SIZE);
		while( fs64 )
		{
			size_t to_read = (size_t)min( fs64, (uint64_t)FRAME_SIZE );
			size_t eff = 0;
			RFAILED( kfs->Read( buffer.data(), to_read, &eff ) );
			if( !proc.update( buffer.data(), eff ) ) return S_FALSE;
			fs64 -= eff;
		}
		return S_OK;
	}

	struct hashes
	{
		uint8_t			md5_[16];
		md5::MD5_CTX	md5_ctx;

		hashes()
		{
			init();
		}

		uint8_t * get_md5( char * md5_str = 0 )
		{
			if( md5_str )
			{
				for( size_t i = 0; i < sizeof(md5_); ++ i )
					sprintf( md5_str + (i<<1), "%02X", md5_[i] );
			}
			return md5_;
		}

	public:

		bool update( const void * buf, size_t bytes )
		{
			md5::MD5Update( &md5_ctx, (const uint8_t*)buf, bytes );
			return true;
		}
		long calc( const wchar_t * file_name )
		{
			RFAILED( init() );
			xm::iptr<xm::IKNativeFile> nfo( xm::PROGID_WinNativeFile );
			RFAILED( nfo->Open( file_name, OFR ) );
			xm::iptr<xm::IKFileStream> kfs( nfo );
			RFAILED( (travel_file_data<0x10000,hashes&>( kfs, *this )) );
			return checkout();
		}
		long calc( const char * file_name )
		{
			RFAILED( init() );
			xm::iptr<xm::IKNativeFile> nfo( xm::PROGID_WinNativeFile );
			RFAILED( nfo->Open( file_name, OFR ) );
			xm::iptr<xm::IKFileStream> kfs( nfo );
			RFAILED( (travel_file_data<0x10000,hashes&>( kfs, *this )) );
			return checkout();
		}
	
		long calc( const void * bfp, size_t bcb )
		{
			RFAILED( init() );
			MD5Update( &md5_ctx, (const uint8_t*)bfp, bcb );
			return checkout();
		}
	protected:

		long init()
		{
			md5::MD5Init(&md5_ctx);
			return S_OK;
		}



		long checkout()
		{
			md5::MD5Final( md5_, &md5_ctx );

			return S_OK;
		}
	};

	long get_file_md5( const char * file, std::string & str_md5 )
	{
		hashes h;
		RFAILED( h.calc( file ) );
		char md5_str[33] = {};
		h.get_md5( md5_str );
		str_md5 = md5_str;
		return S_OK;
	}
	long get_file_md5( const wchar_t * file, std::string & str_md5 )
	{
		hashes h;
		RFAILED( h.calc( file ) );
		char md5_str[33] = {};
		h.get_md5( md5_str );
		str_md5 = md5_str;
		return S_OK;
	}


};

#endif